﻿using System;
using System.Data;
using System.Globalization;
using System.IO;

using PublicDomain.Data.Csv.Metadata;
using PublicDomain.Data.Csv.Properties;

namespace PublicDomain.Data.Csv
{
    public class CsvDataReader : IDataReader
    {
        private const int defaultHeaderRowsCount = 1;
        private const char defaultDelimiter = ',';

        private CsvDataFormat csvDataFormat;
        private char delimiter = ',';
        private int headerRowsCount;
        private CsvParsingOptions parsingOptions;

        private StreamReader streamReader;
        private DataTable schemaTable;

        private string headerLineText;
        private CsvDataRow currentCsvDataRow;
        private int currentDataRowIndex;

        public CsvDataReader(TextReader textReader, CsvDataFormat csvDataFormat, int? headerRowsCount, char? delimiter, CsvParsingOptions parsingOptions)
        {
            this.TextReader = textReader;
            this.csvDataFormat = csvDataFormat;
            this.headerRowsCount = headerRowsCount ?? defaultHeaderRowsCount;
            this.delimiter = delimiter ?? defaultDelimiter;
            this.parsingOptions = parsingOptions;
        }

        public CsvDataReader(string csvFilePath, CsvDataFormat csvDataFormat, int? headerRowsCount, char? delimiter, CsvParsingOptions parsingOptions)
        {
            this.streamReader = new StreamReader(csvFilePath);

            this.TextReader = this.streamReader;
            this.csvDataFormat = csvDataFormat;
            this.headerRowsCount = headerRowsCount ?? defaultHeaderRowsCount;
            this.delimiter = delimiter ?? defaultDelimiter;
            this.parsingOptions = parsingOptions;
        }

        public CsvDataReader(string csvFilePath, CsvDataFormat csvDataFormat)
            : this(csvFilePath, csvDataFormat, defaultHeaderRowsCount)
        {
            this.TextReader = new StreamReader(csvFilePath);
            this.csvDataFormat = csvDataFormat;
            this.IsClosed = true;
        }

        public CsvDataReader(string csvFilePath, CsvDataFormat csvDataFormat, int headerRowCount)
        {
            this.TextReader = new StreamReader(csvFilePath);
            this.csvDataFormat = csvDataFormat;
            this.headerRowsCount = headerRowCount;
            this.IsClosed = true;
        }

        public CsvDataReader(string csvFilePath)
            : this(csvFilePath, null)
        {
        }

        public CsvDataReader(TextReader textReader, CsvDataFormat csvDataFormat)
            : this(textReader, csvDataFormat, defaultHeaderRowsCount)
        {
        }

        public CsvDataReader(TextReader textReader, CsvDataFormat csvDataFormat, int headerRowCount)
        {
            if (textReader == null)
            {
                throw new ArgumentNullException("textReader");
            }

            this.csvDataFormat = csvDataFormat;
            this.TextReader = textReader;
            this.headerRowsCount = headerRowCount;
            this.IsClosed = true;
        }

        public CsvDataReader(TextReader textReader)
            : this(textReader, null)
        {
        }

        public TextReader TextReader { get; private set; }

        public CsvDataFormat CsvDataFormat
        {
            get
            {
                if (this.csvDataFormat == null && !this.IsClosed)
                {
                    this.csvDataFormat = CsvDataFormat.CreateDefaultCsvDataFormat("TextFile.csv", DelimitedString.Split(this.headerLineText, this.delimiter, this.parsingOptions.IsIncluded(CsvParsingOptions.EscapedEmptyAsZeroLengthString)));
                }

                return this.csvDataFormat;
            }
        }

        public CsvParsingOptions ParsingOptions
        {
            get { return this.parsingOptions; } 
        }

        public int ColumnsParsed
        {
            get 
            {
                if (this.IsClosed)
                {
                    return 0;
                }

                if (this.currentCsvDataRow != null && this.currentCsvDataRow.ColumnsTextValues != null)
                {
                    return this.currentCsvDataRow.ActualFieldCount;
                }

                return 0;
            }
        }

        public int CurrentDataRowIndex
        {
            get { return this.currentDataRowIndex; }
        }

        public bool IsClosed { get; private set; }

        public int FieldCount
        {
            get
            {
                if (this.csvDataFormat == null)
                {
                    return -1;
                }

                var result = this.CsvDataFormat.CsvColumnsDefinitions.Count;

                return result;
            }
        }

        public int Depth
        {
            get { return 0; }
        }

        public int RecordsAffected
        {
            get { return -1; }
        }

        public object this[int i]
        {
            get { return this.GetValue(i); }
        }

        public object this[string name]
        {
            get { return this[this.GetOrdinal(name)]; }
        }

        public bool Read()
        {
            // Skip header rows
            while (this.currentDataRowIndex < this.headerRowsCount)
            {
                var currentHeaderLineText = this.TextReader.ReadLine();
                this.currentDataRowIndex++;

                if (this.headerLineText == null) 
                {
                    // Only first row will be used
                    this.headerLineText = currentHeaderLineText;
                }
            }

            var textLine = this.TextReader.ReadLine();
            this.currentDataRowIndex++;

            if (this.headerLineText == null)
            {
                this.headerLineText = textLine;
            }

            var result = textLine != null;

            if (!result)
            {
                this.Close();
                return result;
            }
            else
            {
                this.IsClosed = false;
            }

            var columnsTextValues = DelimitedString.Split(textLine, this.delimiter, this.parsingOptions.IsIncluded(CsvParsingOptions.EscapedEmptyAsZeroLengthString));
            this.currentCsvDataRow = new CsvDataRow(columnsTextValues, this.CsvDataFormat);

            this.ValidateColumnsCount();

            return result;
        }

        public void Close()
        {
            this.IsClosed = true;
            if (this.TextReader != null)
            {
                this.TextReader.Dispose();
                this.TextReader = TextReader.Null;
            }

            if (this.streamReader != null)
            {
                this.streamReader.Dispose();
                this.streamReader = null;
            }

            if (this.schemaTable != null)
            {
                this.schemaTable.Dispose();
                this.schemaTable = null;
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        public DataTable GetSchemaTable()
        {
            if (this.schemaTable == null)
            {
                this.schemaTable = CsvSchemaDataTable.GetSchemaTable(this.CsvDataFormat);
            }

            return this.schemaTable;
        }

        public bool NextResult()
        {
            throw new NotImplementedException();
        }

        public string GetDataTypeName(int i)
        {
            var result = Enum.GetName(typeof(DbType), this.CsvDataFormat.GetCsvColumnSchema(i).DataType);

            return result;
        }

        public string GetName(int i)
        {
            var result = this.CsvDataFormat.GetCsvColumnSchema(i).ColumnName;

            return result;
        }

        public int GetOrdinal(string name)
        {
            var result = this.CsvDataFormat.GetCsvColumnSchema(name).ColumnOrdinal;

            return result;
        }

        public bool GetBoolean(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetBoolean(i);
        }

        public byte GetByte(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetByte(i);
        }

        public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
        }

        public char GetChar(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetChar(i);
        }

        public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetChars(i, fieldoffset, buffer, bufferoffset, length);
        }

        public IDataReader GetData(int i)
        {
            throw new NotImplementedException();
        }

        public DateTime GetDateTime(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetDateTime(i);
        }

        public decimal GetDecimal(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetDecimal(i);
        }

        public double GetDouble(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetDouble(i);
        }

        public Type GetFieldType(int i)
        {
            var result = this.CsvDataFormat.GetCsvColumnSchema(i).FieldType;

            return result;
        }

        public float GetFloat(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetFloat(i);
        }

        public Guid GetGuid(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetGuid(i);
        }

        public short GetInt16(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetInt16(i);
        }

        public int GetInt32(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetInt32(i);
        }

        public long GetInt64(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetInt64(i);
        }

        public string GetString(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetString(i);
        }

        public object GetValue(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetValue(i);
        }

        public int GetValues(object[] values)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetValues(values);
        }

        public bool IsDBNull(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.IsDBNull(i);
        }

#pragma warning disable 3002
        public sbyte GetSByte(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetSByte(i);
        }

        public uint GetUInt16(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetUInt16(i);
        }

        public uint GetUInt32(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetUInt32(i);
        }

        public ulong GetUInt64(int i)
        {
            this.ValidateStatusIsClosed();

            return this.currentCsvDataRow.GetUInt64(i);
        }
#pragma warning restore 3002

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.Close();
            }
        }

        private void ValidateStatusIsClosed()
        {
            if (this.IsClosed)
            {
                throw new CsvDataInvalidOperationException(Resources.DataReaderIsClosedError);
            }
        }

        private void ValidateColumnsCount()
        {
            var actualColumnsCount = this.ColumnsParsed;
            var expectedColumnsCount = this.csvDataFormat.CsvColumnsDefinitions.Count;

            if (actualColumnsCount < expectedColumnsCount && !this.parsingOptions.IsIncluded(CsvParsingOptions.AllowLessColumnsInOneRecord))
            {
                throw new CsvDataInvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.DataRowLessNumberOfColumnsError,
                        this.currentDataRowIndex,
                        actualColumnsCount,
                        expectedColumnsCount));
            }

            if (actualColumnsCount > expectedColumnsCount && !this.parsingOptions.IsIncluded(CsvParsingOptions.AllowMoreColumnsInOneRecord))
            {
                throw new CsvDataInvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.DataRowMoreNumberOfColumnsError,
                        this.currentDataRowIndex,
                        actualColumnsCount,
                        expectedColumnsCount));
            }
        }
    }
}
